package jmine.tec.persist.schema.impl.elements.type;

import java.sql.Types;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import jmine.tec.persist.schema.impl.elements.ColumnTypeElement;
import jmine.tec.persist.schema.impl.elements.Schema;

import org.hibernate.type.Type;

import bancosys.tec.persist.base.enumx.EnumUtils;
import bancosys.tec.persist.base.enumx.IntValuedEnum;

/**
 * Strategy para lidar com campos mapeados para enums apache de valor inteiro
 * 
 * @author lundberg
 */
public class ApacheIntEnumTypeStrategy implements TypeStrategy {

    /**
     * {@inheritDoc}
     */
    public Set<AllowedValue> getAllowedValues(Class<?> returnedClass) {
        Set<AllowedValue> values = new HashSet<AllowedValue>();
        for (IntValuedEnum e : EnumUtils.getApacheEnumValues(returnedClass.asSubclass(IntValuedEnum.class))) {
            values.add(new IntegerAllowedValue(e.getValue(), e.getName().toUpperCase()));
        }
        return values;
    }

    /**
     * {@inheritDoc}
     */
    public boolean hasLimitedAllowedValues() {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public void inferTypeParameters(ColumnTypeElement columnType, Class<?> returnedClass) {
        columnType.setPrecision(this.minimumPrecision(returnedClass));
    }

    /**
     * {@inheritDoc}
     */
    public boolean matches(ColumnTypeElement columnTypeElement, Type type) {
        return IntValuedEnum.class.isAssignableFrom(type.getReturnedClass());
    }

    /**
     * {@inheritDoc}
     */
    public int correctSqlType(Type type, int current) {
        if (current != Types.VARBINARY && current != Types.NUMERIC) {
            Schema.LOG.warn(String.format("Coluna %s mapeada para tipos sql distintos: %s e %s", current, Types.VARBINARY, Types.NUMERIC));
        }
        return Types.NUMERIC;
    }

    /**
     * {@inheritDoc}
     */
    public List<String> checkUserDefinedValues(ColumnTypeElement columnType, Class<?> returnedClass) {
        List<String> warnings = new LinkedList<String>();
        if (columnType.getScale() != 0) {
            warnings.add(String.format("Scale definida para coluna %s, usada por ApacheIntEnum", columnType.getColumn().toString()));
        }
        if (columnType.getPrecision() - columnType.getScale() < this.minimumPrecision(returnedClass)) {
            warnings.add(String.format("Precision definido abaixo do necessário para coluna %s, usada por ApacheIntEnum.", columnType
                    .getColumn().toString()));
            columnType.setPrecision(columnType.getScale() + this.minimumPrecision(returnedClass));
        }
        return warnings;
    }

    /**
     * Encontra a precisão mínima necessária pela coluna para suportar os elementos da enum
     * 
     * @param enumClass enumClass
     * @return int
     */
    private int minimumPrecision(Class<?> enumClass) {
        List<? extends IntValuedEnum> values = EnumUtils.getApacheEnumValues(enumClass.asSubclass(IntValuedEnum.class));
        int max = 0;
        for (Object o : values) {
            int value = ((IntValuedEnum) o).getValue();
            if (value > max) {
                max = value;
            }
        }
        return Integer.toString(max).length();
    }
}